/*  Starshatter OpenSource Distribution
    Copyright (c) 1997-2004, Destroyer Studios LLC.
    All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name "Destroyer Studios" nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    SUBSYSTEM:    Stars.exe
    FILE:         CombatGroup.cpp
    AUTHOR:       John DiCamillo


    OVERVIEW
    ========
    An element in the dynamic campaign
*/

#include "MemDebug.h"
#include "CombatGroup.h"
#include "CombatUnit.h"
#include "CombatZone.h"
#include "Combatant.h"
#include "CombatAssignment.h"
#include "Campaign.h"
#include "ShipDesign.h"
#include "Ship.h"

#include "Game.h"
#include "DataLoader.h"
#include "ParseUtil.h"

// +----------------------------------------------------------------------+

CombatGroup::CombatGroup(int t, int n, const char* s, int iff_code, int e, CombatGroup* p)
: type(t), id(n), name(s), iff(iff_code), enemy_intel(e),
parent(p), value(0), plan_value(0), unit_index(0), combatant(0),
expanded(false), sorties(0), kills(0), points(0),
current_zone(0), assigned_zone(0), zone_lock(false)
{
    if (parent)
    parent->AddComponent(this);
}

CombatGroup::~CombatGroup()
{
    assignments.destroy();
    components.destroy();
    units.destroy();
}

// +--------------------------------------------------------------------+

void
CombatGroup::AddComponent(CombatGroup* g)
{
    if (g) {
        g->parent = this;
        components.append(g);
    }
}

// +--------------------------------------------------------------------+

bool
CombatGroup::IsAssignable() const
{
    switch (type) {
    case CARRIER_GROUP:
    case BATTLE_GROUP:
    case DESTROYER_SQUADRON:
    case ATTACK_SQUADRON:
    case FIGHTER_SQUADRON:
    case INTERCEPT_SQUADRON:
    case LCA_SQUADRON:
        return ((CombatGroup*) this)->CalcValue() > 0;
    }

    return false;
}

bool
CombatGroup::IsTargetable() const
{
    // neutral / non-combatants are not *strategic* targets
    // for any combatant:
    if (iff < 1 || iff >= 100)
    return false;

    // civilian / non-combatant are not strategic targets:
    if (type == PASSENGER || 
            type == PRIVATE   || 
            type == MEDICAL   || 
            type == HABITAT)
    return false;

    // must have units of our own to be targetable:
    if (units.size() < 1)
    return false;

    return ((CombatGroup*) this)->CalcValue() > 0;
}

bool
CombatGroup::IsDefensible() const
{
    if (type >= SUPPORT)
    return ((CombatGroup*) this)->CalcValue() > 0;

    return false;
}

bool
CombatGroup::IsStrikeTarget() const
{
    if (type <  BATTALION ||
            type == MINEFIELD ||   // assault, not strike
            type == PASSENGER || 
            type == PRIVATE   || 
            type == MEDICAL   || 
            type == HABITAT)
    return false;

    return ((CombatGroup*) this)->CalcValue() > 0;
}

// +--------------------------------------------------------------------+

bool
CombatGroup::IsMovable() const
{
    switch (type) {
    case CARRIER_GROUP:
    case BATTLE_GROUP:
    case DESTROYER_SQUADRON:
    case ATTACK_SQUADRON:
    case FIGHTER_SQUADRON:
    case INTERCEPT_SQUADRON:
    case LCA_SQUADRON:
    case COURIER:
    case MEDICAL:
    case SUPPLY:
    case REPAIR:
    case FREIGHT:
    case PASSENGER:
    case PRIVATE:
        return true;
    }

    return false;
}

// +--------------------------------------------------------------------+

bool
CombatGroup::IsFighterGroup() const
{
    switch (type) {
    case WING:
    case INTERCEPT_SQUADRON:
    case FIGHTER_SQUADRON:
    case ATTACK_SQUADRON:
        return true;
    }

    return false;
}

bool
CombatGroup::IsStarshipGroup() const
{
    switch (type) {
    case DESTROYER_SQUADRON:
    case BATTLE_GROUP:
    case CARRIER_GROUP:
        return true;
    }

    return false;
}

// +--------------------------------------------------------------------+

bool
CombatGroup::IsReserve() const
{
    if (enemy_intel <= Intel::RESERVE)
    return true;

    if (parent)
    return parent->IsReserve();

    return false;
}

// +--------------------------------------------------------------------+

const int*
CombatGroup::PreferredAttacker(int type)
{
    static int p[8];

    ZeroMemory(p, sizeof(p));

    switch (type) {
        //case FLEET:
    case DESTROYER_SQUADRON:   p[0] = DESTROYER_SQUADRON;
        p[1] = BATTLE_GROUP;
        p[2] = CARRIER_GROUP;
        p[3] = ATTACK_SQUADRON;
        break;

    case BATTLE_GROUP:         p[0] = BATTLE_GROUP;
        p[1] = DESTROYER_SQUADRON;
        p[2] = CARRIER_GROUP;
        p[3] = ATTACK_SQUADRON;
        break;

    case CARRIER_GROUP:        p[0] = ATTACK_SQUADRON;
        p[1] = BATTLE_GROUP;
        p[2] = DESTROYER_SQUADRON;
        p[3] = CARRIER_GROUP;
        break;

        //case WING:
    case LCA_SQUADRON:
    case ATTACK_SQUADRON:
    case INTERCEPT_SQUADRON:
    case FIGHTER_SQUADRON:     p[0] = INTERCEPT_SQUADRON;
        p[1] = FIGHTER_SQUADRON;
        break;

        //case BATTALION:
    case STATION:              p[0] = BATTLE_GROUP;
        p[1] = CARRIER_GROUP;
        break;

    case STARBASE:
    case BATTERY:
    case MISSILE:              p[0] = ATTACK_SQUADRON;
        p[1] = FIGHTER_SQUADRON;
        break;

        //case C3I:
    case MINEFIELD:
    case COMM_RELAY:
    case EARLY_WARNING:
    case FWD_CONTROL_CTR:
    case ECM:                  p[0] = ATTACK_SQUADRON;
        p[1] = FIGHTER_SQUADRON;
        p[2] = DESTROYER_SQUADRON;
        break;

        //case SUPPORT:
    case COURIER:
    case MEDICAL:
    case SUPPLY:
    case REPAIR:               p[0] = DESTROYER_SQUADRON;
        p[1] = BATTLE_GROUP;
        p[2] = ATTACK_SQUADRON;
        break;

        //case CIVILIAN:

        //case WAR_PRODuCTION:
    case FACTORY:
    case REFINERY:
    case RESOURCE:             p[0] = ATTACK_SQUADRON;
        p[1] = FIGHTER_SQUADRON;
        break;

        //case INFRASTRUCTURE:
    case TRANSPORT:
    case NETWORK:
    case HABITAT:
    case STORAGE:              p[0] = ATTACK_SQUADRON;
        p[1] = FIGHTER_SQUADRON;
        break;

        //case NON_COM:
    case FREIGHT:
    case PASSENGER:
    case PRIVATE:              p[0] = DESTROYER_SQUADRON;
        p[1] = ATTACK_SQUADRON;
        break;
    }

    return p;
}

// +--------------------------------------------------------------------+

const int*
CombatGroup::PreferredDefender(int type)
{
    static int p[8];

    ZeroMemory(p, sizeof(p));

    switch (type) {
        //case FLEET:
    case CARRIER_GROUP:
    case BATTLE_GROUP:
    case DESTROYER_SQUADRON:

        //case WING:
    case LCA_SQUADRON:
    case ATTACK_SQUADRON:
    case INTERCEPT_SQUADRON:
    case FIGHTER_SQUADRON:     break;

        //case BATTALION:
    case STATION:              p[0] = BATTLE_GROUP;
        p[1] = CARRIER_GROUP;
        p[2] = DESTROYER_SQUADRON;
        break;
    case STARBASE:
    case MINEFIELD:
    case BATTERY:
    case MISSILE:              p[0] = FIGHTER_SQUADRON;
        p[1] = INTERCEPT_SQUADRON;
        break;

        //case C3I:
    case COMM_RELAY:
    case EARLY_WARNING:
    case FWD_CONTROL_CTR:
    case ECM:                  p[0] = FIGHTER_SQUADRON;
        p[1] = INTERCEPT_SQUADRON;
        break;

        //case SUPPORT:
    case COURIER:
    case MEDICAL:
    case SUPPLY:
    case REPAIR:               p[0] = DESTROYER_SQUADRON;
        p[1] = BATTLE_GROUP;
        p[2] = ATTACK_SQUADRON;
        break;

        //case CIVILIAN:

        //case WAR_PRODuCTION:
    case FACTORY:
    case REFINERY:
    case RESOURCE:             p[0] = FIGHTER_SQUADRON;
        p[1] = INTERCEPT_SQUADRON;
        break;

        //case INFRASTRUCTURE:
    case TRANSPORT:
    case NETWORK:
    case HABITAT:
    case STORAGE:              p[0] = FIGHTER_SQUADRON;
        p[1] = INTERCEPT_SQUADRON;
        break;

        //case NON_COM:
    case FREIGHT:
    case PASSENGER:
    case PRIVATE:              p[0] = DESTROYER_SQUADRON;
        p[1] = BATTLE_GROUP;
        break;
    }

    return p;
}

// +--------------------------------------------------------------------+

CombatGroup*
CombatGroup::FindGroup(int t, int n)
{
    CombatGroup* result = 0;

    if (type == t && (n < 0 || id == n))
    result = this;

    ListIter<CombatGroup> group = components;
    while (!result && ++group) {
        result = group->FindGroup(t, n);
    }

    return result;
}

// +--------------------------------------------------------------------+

CombatGroup*
CombatGroup::Clone(bool deep)
{
    CombatGroup* clone = new(__FILE__,__LINE__)
    CombatGroup(type, id, name, iff, enemy_intel);

    clone->combatant  = combatant;
    clone->region     = region;
    clone->location   = location;
    clone->value      = value;
    clone->expanded   = expanded;

    for (int i = 0; i < units.size(); i++) {
        CombatUnit* u = new(__FILE__,__LINE__) CombatUnit(*units[i]);
        u->SetCombatGroup(clone);
        clone->units.append(u);
    }

    if (deep) {
        for (int i = 0; i < components.size(); i++) {
            CombatGroup* g = components[i]->Clone(deep);
            clone->AddComponent(g);

            if (g->Type() == FIGHTER_SQUADRON   ||
                    g->Type() == INTERCEPT_SQUADRON ||
                    g->Type() == ATTACK_SQUADRON    ||
                    g->Type() == LCA_SQUADRON) {

                if (units.size() > 0) {
                    CombatUnit* carrier = units[0];

                    for (int u = 0; u < g->GetUnits().size(); u++) {
                        CombatUnit* unit = g->GetUnits()[u];

                        if (unit->Type() >= Ship::FIGHTER ||
                                unit->Type() <= Ship::LCA) {
                            unit->SetCarrier(carrier);
                            unit->SetRegion(carrier->GetRegion());
                        }
                    }
                }
            }
        }
    }

    return clone;
}

// +--------------------------------------------------------------------+

const char*
CombatGroup::GetOrdinal() const
{
    static char ordinal[16];

    int last_two_digits = id % 100;

    if (last_two_digits > 10 && last_two_digits < 20) {
        sprintf_s(ordinal, "ordinal.%d", last_two_digits);
        Text suffix = Game::GetText(ordinal);
        
        if (suffix != ordinal)
            sprintf_s(ordinal, "%d%s", id, suffix.data());
        else
            sprintf_s(ordinal, "%dth", id);
    }
    else {
        int last_digit = last_two_digits % 10;
        sprintf_s(ordinal, "ordinal.%d", last_digit);
        Text suffix = Game::GetText(ordinal);
        if (suffix != ordinal)
            sprintf_s(ordinal, "%d%s", id, suffix.data());
        else if (last_digit == 1)
            sprintf_s(ordinal, "%dst", id);
        else if (last_digit == 2)
            sprintf_s(ordinal, "%dnd", id);
        else if (last_digit == 3)
            sprintf_s(ordinal, "%drd", id);
        else
            sprintf_s(ordinal, "%dth", id);
    }

    return ordinal;
}

const char*
CombatGroup::GetDescription() const
{
    static char desc[256];
    static char name_desc[256];

    if (name.length())
        sprintf_s(name_desc, " \"%s\"", (const char*) name);
    else
        name_desc[0] = 0;

    switch (type) {
    case FORCE:                strcpy_s(desc, (const char*) name); break;

    case FLEET:                sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FLEET").data(), name_desc); break;
    case CARRIER_GROUP:        sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.CARRIER_GROUP").data(), name_desc); break;
    case BATTLE_GROUP:         sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTLE_GROUP").data(), name_desc); break;
    case DESTROYER_SQUADRON:   sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.DESTROYER_SQUADRON").data(), name_desc); break;

    case WING:                 sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.WING").data(), name_desc); break;
    case ATTACK_SQUADRON:      sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.ATTACK_SQUADRON").data(), name_desc); break;
    case FIGHTER_SQUADRON:     sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FIGHTER_SQUADRON").data(), name_desc); break;
    case INTERCEPT_SQUADRON:   sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.INTERCEPT_SQUADRON").data(), name_desc); break;
    case LCA_SQUADRON:         sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.LCA_SQUADRON").data(), name_desc); break;

    case BATTALION:            sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTALION").data(), name_desc); break;
    case STATION:              sprintf_s(desc, "%s %s", Game::GetText("CombatGroup.STATION").data(), name.data()); break;
    case STARBASE:             sprintf_s(desc, "%s %d%s", Game::GetText("CombatGroup.STARBASE").data(), id, name_desc); break;
    case MINEFIELD:            sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MINEFIELD").data(), name_desc); break;
    case BATTERY:              sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTERY").data(), name_desc); break;
    case MISSILE:              sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MISSILE").data(), name_desc); break;

    case C3I:                  sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.C3I").data(), name_desc); break;
    case COMM_RELAY:           sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.COMM_RELAY").data(), name_desc); break;
    case EARLY_WARNING:        sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.EARLY_WARNING").data(), name_desc); break;
    case FWD_CONTROL_CTR:      sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FWD_CONTROL_CTR").data(), name_desc); break;
    case ECM:                  sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.ECM").data(), name_desc); break;

    case SUPPORT:              sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.SUPPORT").data(), name_desc); break;
    case COURIER:              sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.COURIER").data(), name_desc); break;
    case SUPPLY:               sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.SUPPLY").data(), name_desc); break;
    case REPAIR:               sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.REPAIR").data(), name_desc); break;
    case MEDICAL:              sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MEDICAL").data(), name_desc); break;

    case CIVILIAN:
    case WAR_PRODUCTION:       
    case FACTORY:
    case REFINERY:
    case RESOURCE:             strcpy_s(desc, (const char*) name); break;

    case INFRASTRUCTURE:
    case TRANSPORT:
    case NETWORK:
    case HABITAT:
    case STORAGE:
    case FREIGHT:
    case PASSENGER:
    case PRIVATE:              strcpy_s(desc, (const char*) name); break;

    default:                   sprintf_s(desc, "%s%s", Game::GetText("CombatGroup.default").data(), name_desc); break;
    }

    return desc;
}

const char*
CombatGroup::GetShortDescription() const
{
    static char desc[256];

    switch (type) {
    case FORCE:                strcpy_s(desc, (const char*) name); break;

    case FLEET:                sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FLEET").data()); break;
    case CARRIER_GROUP:        sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.CARRIER_GROUP").data()); break;
    case BATTLE_GROUP:         sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTLE_GROUP").data()); break;
    case DESTROYER_SQUADRON:   sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.DESTROYER_SQUADRON").data()); break;

    case WING:                 sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.WING").data()); break;
    case ATTACK_SQUADRON:      sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.ATTACK_SQUADRON").data()); break;
    case FIGHTER_SQUADRON:     sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FIGHTER_SQUADRON").data()); break;
    case INTERCEPT_SQUADRON:   sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.INTERCEPT_SQUADRON").data()); break;
    case LCA_SQUADRON:         sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.LCA_SQUADRON").data()); break;

    case BATTALION:            sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTALION").data()); break;
    case STATION:              sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.STATION").data()); break;
    case STARBASE:             sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.STARBASE").data()); break;
    case MINEFIELD:            sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.MINEFIELD").data()); break;
    case BATTERY:              sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTERY").data()); break;

    case C3I:                  sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.C3I").data()); break;
    case COMM_RELAY:           sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.COMM_RELAY").data()); break;
    case EARLY_WARNING:        sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.EARLY_WARNING").data()); break;
    case FWD_CONTROL_CTR:      sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FWD_CONTROL_CTR").data()); break;
    case ECM:                  sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.ECM").data()); break;

    case SUPPORT:              sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.SUPPORT").data()); break;
    case COURIER:              sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.COURIER").data()); break;
    case MEDICAL:              sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.MEDICAL").data()); break;
    case SUPPLY:               sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.SUPPLY").data()); break;
    case REPAIR:               sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.REPAIR").data()); break;

    case CIVILIAN:
    case WAR_PRODUCTION:       
    case FACTORY:
    case REFINERY:
    case RESOURCE:             strcpy_s(desc, (const char*) name); break;

    case INFRASTRUCTURE:
    case TRANSPORT:
    case NETWORK:
    case HABITAT:
    case STORAGE:
    case FREIGHT:
    case PASSENGER:
    case PRIVATE:              strcpy_s(desc, (const char*) name); break;

    default:                   sprintf_s(desc, "%s", Game::GetText("CombatGroup.abrv.default").data()); break;
    }

    return desc;
}

// +--------------------------------------------------------------------+

double
CombatGroup::GetNextJumpTime() const
{
    double t = 0;

    ListIter<CombatUnit> unit = ((CombatGroup*) this)->units;
    while (++unit)
    if (unit->GetNextJumpTime() > t)
    t = unit->GetNextJumpTime();

    return t;
}

// +--------------------------------------------------------------------+

void
CombatGroup::MoveTo(const Point& loc)
{
    location = loc;
}

// +--------------------------------------------------------------------+

void
CombatGroup::SetAssignedSystem(const char* s)
{
    assigned_system = s;
    assigned_zone   = 0;
    zone_lock       = false;

    ListIter<CombatGroup> iter = components;
    while (++iter) {
        CombatGroup* g = iter.value();
        g->SetAssignedSystem(s);
    }
}

void
CombatGroup::SetAssignedZone(CombatZone* z)
{
    assigned_zone   = z;

    if (!assigned_zone)
    zone_lock = false;

    ListIter<CombatGroup> iter = components;
    while (++iter) {
        CombatGroup* g = iter.value();
        g->SetAssignedZone(z);
    }
}

void
CombatGroup::ClearUnlockedZones()
{
    if (!zone_lock)
    assigned_zone = 0;

    ListIter<CombatGroup> iter = components;
    while (++iter) {
        CombatGroup* g = iter.value();
        g->ClearUnlockedZones();
    }
}

void
CombatGroup::SetZoneLock(bool lock)
{
    if (!assigned_zone)
    zone_lock = false;
    else
    zone_lock = lock;

    if (zone_lock)
    assigned_system = Text();

    ListIter<CombatGroup> iter = components;
    while (++iter) {
        CombatGroup* g = iter.value();
        g->SetZoneLock(lock);
    }
}

// +--------------------------------------------------------------------+

void
CombatGroup::SetIntelLevel(int n)
{
    if (n < Intel::RESERVE || n > Intel::TRACKED) return;

    enemy_intel = n;

    // if this group has been discovered, the entire
    // branch of the OOB tree must be exposed.  Otherwise,
    // no missions would ever be planned against this
    // combat group.

    if (n > Intel::SECRET) {
        CombatGroup* p = parent;
        while (p) {
            if (p->enemy_intel < Intel::KNOWN)
            p->enemy_intel = Intel::KNOWN;

            p = p->parent;
        }
    }
}

// +--------------------------------------------------------------------+

int
CombatGroup::CalcValue()
{
    int val = 0;

    ListIter<CombatUnit> unit = units;
    while (++unit)
    val += unit->GetValue();

    ListIter<CombatGroup> comp = components;
    while (++comp)
    val += comp->CalcValue();

    value = val;
    return value;
}

int
CombatGroup::CountUnits() const
{
    int n = 0;

    CombatGroup* g = (CombatGroup*) this;

    ListIter<CombatUnit> unit = g->units;
    while (++unit)
    n += unit->Count() - unit->DeadCount();

    CombatGroup* pThis = ((CombatGroup*) this);
    pThis->live_comp.clear();
    ListIter<CombatGroup> iter = g->components;
    while (++iter) {
        CombatGroup* comp = iter.value();

        if (!comp->IsReserve()) {
            int unit_count = comp->CountUnits();
            if (unit_count > 0)
            pThis->live_comp.append(comp);

            n += unit_count;
        }
    }

    return n;
}

// +--------------------------------------------------------------------+

void
CombatGroup::ClearAssignments()
{
    assignments.destroy();

    ListIter<CombatGroup> comp = components;
    while (++comp)
    comp->ClearAssignments();
}

// +--------------------------------------------------------------------+

CombatGroup*
CombatGroup::FindCarrier()
{
    CombatGroup* p = GetParent();

    while (p         != 0                           && 
    p->Type() != CombatGroup::CARRIER_GROUP  &&
    p->Type() != CombatGroup::STATION        &&
    p->Type() != CombatGroup::STARBASE)
    p = p->GetParent();

    if (p && p->GetUnits().size())
    return p;

    return 0;
}

CombatUnit*
CombatGroup::GetRandomUnit()
{
    CombatUnit*       result = 0;
    List<CombatUnit>  live;

    ListIter<CombatUnit> unit = units;
    while (++unit) {
        if (unit->Count() - unit->DeadCount() > 0)
        live.append(unit.value());
    }

    if (live.size() > 0) {
        int ntries = 5;
        while (!result && ntries-- > 0) {
            int index = rand() % live.size();
            result = live[index];

            int ship_class = result->GetShipClass();
            if (ship_class >= Ship::CRUISER &&
                    ship_class <= Ship::FARCASTER)
            result = 0;
        }
    }

    if (!result) {
        ListIter<CombatGroup> comp = components;
        while (++comp && !result) {
            CombatUnit* u = comp->GetRandomUnit();
            if (u)
            result = u;
        }
    }

    return result;
}

CombatUnit*
CombatGroup::GetFirstUnit()
{
    int tmp_index = unit_index;
    unit_index = 0;
    CombatUnit* result = GetNextUnit();
    unit_index = tmp_index;

    return result;
}

CombatUnit*
CombatGroup::GetNextUnit()
{
    if (units.size() > 0) {
        List<CombatUnit> live;

        ListIter<CombatUnit> unit = units;
        while (++unit) {
            if (unit->Count() - unit->DeadCount() > 0)
            live.append(unit.value());
        }

        if (live.size() > 0) {
            return live[unit_index++ % live.size()];
        }
    }

    if (components.size() > 0) {
        return components[unit_index % components.size()]->GetNextUnit();
    }

    return 0;
}

CombatUnit*
CombatGroup::FindUnit(const char* name)
{
    if (units.size() > 0) {
        ListIter<CombatUnit> iter = units;
        while (++iter) {
            CombatUnit* unit = iter.value();
            if (unit->Name() == name) {
                if (unit->Count() - unit->DeadCount() > 0)
                return unit;
                else
                return 0;
            }
        }
    }

    return 0;
}

void
CombatGroup::AssignRegion(Text rgn)
{
    region = rgn;

    ListIter<CombatGroup> comp = components;
    while (++comp)
    comp->AssignRegion(rgn);

    ListIter<CombatUnit> unit = units;
    while (++unit)
    unit->SetRegion(rgn);
}

// +--------------------------------------------------------------------+

static const char* group_name[] = {
    "",
    "force",
    "wing",
    "intercept_squadron",
    "fighter_squadron",
    "attack_squadron",
    "lca_squadron",
    "fleet",
    "destroyer_squadron",
    "battle_group",
    "carrier_group",
    "battalion",
    "minefield",
    "battery",
    "missile",
    "station",
    "starbase",
    "c3i",
    "comm_relay",
    "early_warning",
    "fwd_control_ctr",
    "ecm",
    "support",
    "courier",
    "medical",
    "supply",
    "repair",
    "civilian",
    "war_production",
    "factory",
    "refinery",
    "resource",
    "infrastructure",
    "transport",
    "network",
    "habitat",
    "storage",
    "non_com",
    "freight",
    "passenger",
    "private"
};

// +--------------------------------------------------------------------+

int
CombatGroup::TypeFromName(const char* type_name)
{
    for (int i = FORCE; i < PRIVATE; i++)
    if (!_stricmp(type_name, group_name[i]))
    return i;

    return 0;
}

const char*
CombatGroup::NameFromType(int type)
{
    return group_name[type];
}

// +--------------------------------------------------------------------+

int ShipClassFromName(const char* type_name)
{
    return Ship::ClassForName(type_name);
}

// +--------------------------------------------------------------------+

#define GET_DEF_BOOL(n) if (pdef->name()->value()==(#n)) GetDefBool((n),   pdef, filename)
#define GET_DEF_TEXT(n) if (pdef->name()->value()==(#n)) GetDefText((n),   pdef, filename)
#define GET_DEF_NUM(n)  if (pdef->name()->value()==(#n)) GetDefNumber((n), pdef, filename)
#define GET_DEF_VEC(n)  if (pdef->name()->value()==(#n)) GetDefVec((n),    pdef, filename)

CombatGroup*
CombatGroup::LoadOrderOfBattle(const char* filename, int team, Combatant* combatant)
{
    CombatGroup* force = 0;
    DataLoader* loader = DataLoader::GetLoader();
    BYTE* block;
    loader->LoadBuffer(filename, block, true);

    Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block));
    Term*  term = parser.ParseTerm();

    if (!term) {
        Print("ERROR: could not parse order of battle '%s'\n", filename);
        return 0;
    }
    else {
        TermText* file_type = term->isText();
        if (!file_type || file_type->value() != "ORDER_OF_BATTLE") {
            Print("ERROR: invalid Order of Battle file '%s'\n", filename);
            term->print(10);
            return 0;
        }
    }


    do {
        delete term; term = 0;
        term = parser.ParseTerm();
        
        if (term) {
            TermDef* def = term->isDef();
            if (def) {
                if (def->name()->value() == "group") {
                    if (!def->term() || !def->term()->isStruct()) {
                        Print("WARNING: group struct missing in '%s'\n", filename);
                    }
                    else {
                        TermStruct* val = def->term()->isStruct();

                        char  name[256];
                        char  type[64];
                        char  intel[64];
                        char  region[64];
                        char  system[64];
                        char  parent_type[64];
                        int   parent_id = 0;
                        int   id  = 0;
                        int   iff = -1;
                        Vec3  loc = Vec3(1.0e9f,0.0f,0.0f);

                        List<CombatUnit>  unit_list;
                        char              unit_name[64];
                        char              unit_regnum[16];
                        char              unit_design[64];
                        char              unit_skin[64];
                        int               unit_class  = 0;
                        int               unit_count  = 1;
                        int               unit_dead   = 0;
                        int               unit_damage = 0;
                        int               unit_heading= 0;
                        int               unit_index  = 0;
                        
                        *name          = 0;
                        *type          = 0;
                        *intel         = 0;
                        *region        = 0;
                        *system        = 0;
                        *parent_type   = 0;
                        *unit_name     = 0;
                        *unit_regnum   = 0;
                        *unit_design   = 0;
                        *unit_skin     = 0;

                        strcpy_s(intel, "KNOWN");

                        // all groups in this OOB default to the IFF of the main force
                        if (force)
                        iff = force->GetIFF();
                        
                        for (int i = 0; i < val->elements()->size(); i++) {
                            TermDef* pdef = val->elements()->at(i)->isDef();
                            if (pdef && (iff < 0 || team < 0 || iff == team)) {
                                GET_DEF_TEXT(name);
                                else GET_DEF_TEXT(type);
                                else GET_DEF_TEXT(intel);
                                else GET_DEF_TEXT(region);
                                else GET_DEF_TEXT(system);
                                else GET_DEF_VEC(loc);
                                else GET_DEF_TEXT(parent_type);
                                else GET_DEF_NUM(parent_id);
                                else GET_DEF_NUM(iff);
                                else GET_DEF_NUM(id);
                                else GET_DEF_NUM(unit_index);

                                else if ((iff == team || team < 0) && pdef->name()->value() == "unit") {
                                    if (!pdef->term() || !pdef->term()->isStruct()) {
                                        Print("WARNING: unit struct missing for group '%s' in '%s'\n", name, filename);
                                    }
                                    else {
                                        TermStruct* val = pdef->term()->isStruct();

                                        char unit_region[64];
                                        char design[256];
                                        Vec3 unit_loc = Vec3(1.0e9f,0.0f,0.0f);
                                        unit_count = 1;

                                        ZeroMemory(unit_region, sizeof(unit_region));
                                        ZeroMemory(design, sizeof(design));
                                        
                                        for (int i = 0; i < val->elements()->size(); i++) {
                                            TermDef* pdef = val->elements()->at(i)->isDef();
                                            if (pdef) {
                                                if (pdef->name()->value() == "name") {
                                                    GetDefText(unit_name, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "regnum") {
                                                    GetDefText(unit_regnum, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "region") {
                                                    GetDefText(unit_region, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "loc") {
                                                    GetDefVec(unit_loc, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "type") {
                                                    char typestr[32];
                                                    GetDefText(typestr, pdef, filename);
                                                    unit_class = ShipDesign::ClassForName(typestr);
                                                }
                                                else if (pdef->name()->value() == "design") {
                                                    GetDefText(unit_design, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "skin") {
                                                    GetDefText(unit_skin, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "count") {
                                                    GetDefNumber(unit_count, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "dead_count") {
                                                    GetDefNumber(unit_dead, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "damage") {
                                                    GetDefNumber(unit_damage, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "heading") {
                                                    GetDefNumber(unit_heading, pdef, filename);
                                                }
                                            }
                                        }

                                        if (!ShipDesign::CheckName(unit_design)) {
                                            Print("ERROR: invalid design '%s' for unit '%s' in '%s'\n", unit_design, unit_name, filename);
                                            return 0;
                                        }

                                        CombatUnit* cu = new(__FILE__,__LINE__) CombatUnit(unit_name, unit_regnum, unit_class, unit_design, unit_count, iff);
                                        cu->SetRegion(unit_region);
                                        cu->SetSkin(unit_skin);
                                        cu->MoveTo(unit_loc);
                                        cu->Kill(unit_dead);
                                        cu->SetSustainedDamage(unit_damage);
                                        cu->SetHeading(unit_heading * DEGREES);
                                        unit_list.append(cu);
                                    }
                                }
                            }
                        }  // elements

                        if (iff >= 0 && (iff == team || team < 0)) {
                            CombatGroup* parent_group = 0;

                            if (force) {
                                parent_group = force->FindGroup(TypeFromName(parent_type), parent_id);
                            }

                            CombatGroup* g = new(__FILE__,__LINE__)
                            CombatGroup(TypeFromName(type), id, name, iff, Intel::IntelFromName(intel), parent_group);

                            g->region     = region;
                            g->combatant  = combatant;
                            g->unit_index = unit_index;

                            if (loc.x >= 1e9) {
                                if (parent_group)
                                g->location = parent_group->location;
                                else
                                g->location = Vec3(0,0,0);
                            }
                            else {
                                g->location = loc;
                            }

                            if (unit_list.size()) {
                                unit_list[0]->SetLeader(true);

                                ListIter<CombatUnit> u = unit_list;
                                while (++u) {
                                    u->SetCombatGroup(g);

                                    if (u->GetRegion().length() < 1) {
                                        u->SetRegion(g->GetRegion());
                                        u->MoveTo(g->Location());
                                    }

                                    if (parent_group &&
                                            (u->Type() == Ship::FIGHTER ||
                                                u->Type() == Ship::ATTACK)) {

                                        CombatUnit*  carrier = 0;
                                        CombatGroup* p       = parent_group;

                                        while (p && !carrier) {
                                            if (p->units.size() && p->units[0]->Type() == Ship::CARRIER) {
                                                carrier = p->units[0];
                                                u->SetCarrier(carrier);
                                                u->SetRegion(carrier->GetRegion());
                                            }

                                            p = p->parent;
                                        }
                                    }
                                }

                                g->units.append(unit_list);
                            }

                            if (!force)
                            force = g;
                        }  // iff == team?
                    }     // group-struct
                }        // group
            }           // def
        }              // term
    }
    while (term);

    loader->ReleaseBuffer(block);
    Print("Order of Battle Loaded (%s).\n", force ? force->Name().data() : "unknown force");

    if (force)
    force->CalcValue();

    return force;
}

// +--------------------------------------------------------------------+

void
CombatGroup::MergeOrderOfBattle(BYTE* block, const char* filename, int team, Combatant* combatant, Campaign* campaign)
{
    CombatGroup* force = 0;

    Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block));
    Term*  term = parser.ParseTerm();

    if (!term) {
        Print("ERROR: could not parse order of battle '%s'\n", filename);
        return;
    }
    else {
        TermText* file_type = term->isText();
        if (!file_type || file_type->value() != "SAVEGAME") {
            Print("ERROR: invalid Save Game file '%s'\n", filename);
            term->print(10);
            return;
        }
    }


    do {
        delete term; term = 0;
        term = parser.ParseTerm();
        
        if (term) {
            TermDef* def = term->isDef();
            if (def) {
                if (def->name()->value() == "group") {
                    if (!def->term() || !def->term()->isStruct()) {
                        Print("WARNING: group struct missing in '%s'\n", filename);
                    }
                    else {
                        TermStruct* val = def->term()->isStruct();

                        char  name[256];
                        char  type[64];
                        char  intel[64];
                        char  region[64];
                        char  system[64];
                        char  zone[64];
                        bool  zone_locked = false;
                        int   id  = 0;
                        int   iff = -1;
                        int   sorties = -1;
                        int   kills   = -1;
                        int   points  = -1;
                        Vec3  loc = Vec3(1.0e9f,0.0f,0.0f);

                        List<CombatUnit>  unit_list;
                        char              unit_name[64];
                        char              unit_regnum[16];
                        char              unit_design[64];
                        int               unit_class  = 0;
                        int               unit_count  = 1;
                        int               unit_dead   = 0;
                        int               unit_damage = 0;
                        int               unit_heading= 0;
                        int               unit_index  = 0;
                        
                        *name          = 0;
                        *type          = 0;
                        *intel         = 0;
                        *region        = 0;
                        *system        = 0;
                        *zone          = 0;
                        *unit_name     = 0;
                        *unit_regnum   = 0;
                        *unit_design   = 0;

                        strcpy_s(intel, "KNOWN");

                        // all groups in this OOB default to the IFF of the main force
                        if (force)
                        iff = force->GetIFF();
                        
                        for (int i = 0; i < val->elements()->size(); i++) {
                            TermDef* pdef = val->elements()->at(i)->isDef();
                            if (pdef && (iff < 0 || team < 0 || iff == team)) {
                                GET_DEF_TEXT(name);
                                else GET_DEF_TEXT(type);
                                else GET_DEF_TEXT(intel);
                                else GET_DEF_TEXT(region);
                                else GET_DEF_TEXT(system);
                                else GET_DEF_TEXT(zone);
                                else GET_DEF_BOOL(zone_locked);
                                else GET_DEF_VEC(loc);
                                else GET_DEF_NUM(iff);
                                else GET_DEF_NUM(id);
                                else GET_DEF_NUM(sorties);
                                else GET_DEF_NUM(kills);
                                else GET_DEF_NUM(points);
                                else GET_DEF_NUM(unit_index);

                                else if ((iff == team || team < 0) && pdef->name()->value() == "unit") {
                                    if (!pdef->term() || !pdef->term()->isStruct()) {
                                        Print("WARNING: unit struct missing for group '%s' in '%s'\n", name, filename);
                                    }
                                    else {
                                        TermStruct* val = pdef->term()->isStruct();

                                        char unit_region[64];
                                        char design[256];
                                        Vec3 unit_loc=Vec3(0.0f,0.0f,0.0f);
                                        unit_count = 1;

                                        ZeroMemory(unit_region, sizeof(unit_region));
                                        ZeroMemory(design, sizeof(design));
                                        
                                        for (int i = 0; i < val->elements()->size(); i++) {
                                            TermDef* pdef = val->elements()->at(i)->isDef();
                                            if (pdef) {
                                                if (pdef->name()->value() == "name") {
                                                    GetDefText(unit_name, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "regnum") {
                                                    GetDefText(unit_regnum, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "region") {
                                                    GetDefText(unit_region, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "loc") {
                                                    GetDefVec(unit_loc, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "type") {
                                                    char typestr[32];
                                                    GetDefText(typestr, pdef, filename);
                                                    unit_class = ShipDesign::ClassForName(typestr);
                                                }
                                                else if (pdef->name()->value() == "design") {
                                                    GetDefText(unit_design, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "count") {
                                                    GetDefNumber(unit_count, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "dead_count") {
                                                    GetDefNumber(unit_dead, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "damage") {
                                                    GetDefNumber(unit_damage, pdef, filename);
                                                }
                                                else if (pdef->name()->value() == "heading") {
                                                    GetDefNumber(unit_heading, pdef, filename);
                                                }
                                            }
                                        }

                                        if (!ShipDesign::CheckName(unit_design)) {
                                            Print("ERROR: invalid design '%s' for unit '%s' in '%s'\n", unit_design, unit_name, filename);
                                            return;
                                        }

                                        if (force) {
                                            CombatUnit* cu = new(__FILE__,__LINE__) CombatUnit(unit_name, unit_regnum, unit_class, unit_design, unit_count, iff);
                                            cu->SetRegion(unit_region);
                                            cu->MoveTo(unit_loc);
                                            cu->Kill(unit_dead);
                                            cu->SetSustainedDamage(unit_damage);
                                            cu->SetHeading(unit_heading * DEGREES);
                                            unit_list.append(cu);
                                        }
                                    }
                                }
                            }
                        }  // elements

                        if (iff >= 0 && (iff == team || team < 0)) {
                            // have we found the force group we are looking for yet?
                            if (!force && !_stricmp(name, combatant->Name())) {
                                force = combatant->GetForce();
                            }

                            else {
                                if (!force)
                                continue;

                                // if we already have a force, and we find a second one, 
                                // it must be the start of a different combatant.
                                // So don't process any further:
                                if (TypeFromName(type) == CombatGroup::FORCE) {
                                    break;
                                }
                            }

                            CombatGroup* g = force->FindGroup(TypeFromName(type), id);

                            if (!g) {
                                ::Print("WARNING: unexpected combat group %s %d '%s' in '%s'\n", type, id, name, filename);
                                continue;
                            }

                            g->region      = region;
                            g->combatant   = combatant;
                            g->location    = loc;
                            g->enemy_intel = Intel::IntelFromName(intel);
                            g->unit_index  = unit_index;

                            if (*zone) {
                                CombatZone* combat_zone = campaign->GetZone(zone);

                                if (combat_zone) {
                                    g->SetAssignedZone(combat_zone);
                                    g->SetZoneLock(zone_locked);
                                }
                                else {
                                    ::Print("WARNING: could not find combat zone '%s' for group %s %d '%s' in '%s'\n", zone, type, id, name, filename);
                                }
                            }
                            else if (*system) {
                                g->SetAssignedSystem(system);
                            }

                            if (sorties >= 0) g->SetSorties(sorties);
                            if (kills   >= 0) g->SetKills(kills);
                            if (points  >= 0) g->SetPoints(points);

                            if (unit_list.size()) {
                                ListIter<CombatUnit> u_iter = unit_list;
                                while (++u_iter) {
                                    CombatUnit* load_unit = u_iter.value();
                                    CombatUnit* u         = g->FindUnit(load_unit->Name());

                                    if (u) {
                                        if (load_unit->GetRegion().length() > 0) {
                                            u->SetRegion(load_unit->GetRegion());
                                            u->MoveTo(load_unit->Location());
                                        }
                                        else {
                                            u->SetRegion(g->GetRegion());
                                            u->MoveTo(g->Location());
                                        }
                                        u->SetDeadCount(load_unit->DeadCount());
                                        u->SetSustainedDamage(load_unit->GetSustainedDamage());
                                        u->SetHeading(load_unit->GetHeading());
                                    }
                                }

                                unit_list.destroy();
                            }

                            if (!force)
                            force = g;
                        }  // iff == team?
                    }     // group-struct
                }        // group
            }           // def
        }              // term
    }
    while (term);

    Print("Order of Battle Loaded (%s).\n", force ? force->Name().data() : "unknown force");

    if (force)
    force->CalcValue();
}

// +--------------------------------------------------------------------+

Text FormatNumber(double n)
{
    char buffer[64];

    if (fabs(n) < 1000)
    sprintf_s(buffer, "%d", (int) n);

    else if (fabs(n) < 1e6) {
        int nn = (int) n / 1000;
        sprintf_s(buffer, "%de3", nn);
    }

    else
    sprintf_s(buffer, "%g", n);

    return buffer;
}

void
SaveCombatUnit(FILE* f, CombatUnit* u)
{
    int type = u->Type();

    if (type == 0 && u->GetDesign())
    type = u->GetDesign()->type;

    fprintf(f, "\n unit: {");
    fprintf(f, " name: \"%s\",",      u->Name().data());
    fprintf(f, " type: \"%s\",",      Ship::ClassName(type));
    fprintf(f, " design: \"%s\",",    u->DesignName().data());

    if (u->Count() > 1) {
        fprintf(f, " count: %d,",      u->Count());
    }
    else {
        fprintf(f, " regnum:\"%s\",", u->Registry().data());
    }

    if (u->GetRegion().length() > 0) {
        fprintf(f, " region:\"%s\",", u->GetRegion().data());

        Text x = FormatNumber(u->Location().x);
        Text y = FormatNumber(u->Location().y);
        Text z = FormatNumber(u->Location().z);

        fprintf(f, " loc:(%s, %s, %s),", x.data(), y.data(), z.data());
    }

    fprintf(f, " dead_count: %d, damage: %d, heading: %d },",
    (int) u->DeadCount(), 
    (int) u->GetSustainedDamage(), 
    (int) (u->GetHeading() / DEGREES));
}

void
SaveCombatGroup(FILE* f, CombatGroup* g)
{
    fprintf(f, "group: {");
    fprintf(f, " type: %s,",       CombatGroup::NameFromType(g->Type()));
    fprintf(f, " id: %d,",         g->GetID());
    fprintf(f, " name: \"%s\",",   g->Name().data());
    fprintf(f, " intel: %s,",      Intel::NameFromIntel(g->IntelLevel()));
    fprintf(f, " iff: %d,",        g->GetIFF());
    fprintf(f, " unit_index: %d,", g->UnitIndex());

    if (g->GetRegion().length()) {
        fprintf(f, " region:\"%s\",", g->GetRegion().data());
    }

    if (g->GetAssignedSystem().length()) {
        fprintf(f, " system: \"%s\",",  g->GetAssignedSystem().data());
    }

    if (g->GetAssignedZone()) {
        fprintf(f, " zone: \"%s\",",    g->GetAssignedZone()->Name().data());
        if (g->IsZoneLocked()) {
            fprintf(f, " zone_locked: true,");
        }
    }

    Text x = FormatNumber(g->Location().x);
    Text y = FormatNumber(g->Location().y);
    Text z = FormatNumber(g->Location().z);

    fprintf(f, " loc: (%s, %s, %s),", x.data(), y.data(), z.data());

    CombatGroup* parent = g->GetParent();
    if (parent) {
        fprintf(f, " parent_type:%s,", CombatGroup::NameFromType(parent->Type()));
        fprintf(f, " parent_id:%d,",   parent->GetID());
    }

    fprintf(f, " sorties: %d,", g->Sorties());
    fprintf(f, " kills: %d,",   g->Kills());
    fprintf(f, " points: %d,",  g->Points());

    ListIter<CombatUnit> u = g->GetUnits();
    while (++u) {
        SaveCombatUnit(f, u.value());
    }

    fprintf(f, " }\n");

    ListIter<CombatGroup> c = g->GetComponents();
    while (++c) {
        SaveCombatGroup(f, c.value());
    }
}

void
CombatGroup::SaveOrderOfBattle(const char* filename, CombatGroup* force)
{
    FILE* f;
    ::fopen_s(&f, filename, "a+");

    if (f) {
        SaveCombatGroup(f, force);
        fprintf(f, "\n");
        fclose(f);
    }
}
